Warn when companion extensions are too old for inline-script envs - #1777
Conversation
A PEP 723 inline-script environment is built correctly by this extension alone, but it only reaches the language server once ms-python.python resolves interpreters per file (exactResource, PR #26129) and Pylance handles the file-scoped python/didChangeFilePythonPath notification (PR #9302). Without both, setup reports success and the user is left without the full inline script experience and no explanation. Warn once, after a successful setup rather than before one, since that is the moment the gap becomes visible and the creation progress notification is already gone. Both the CodeLens command and the bulk command prompt at the handler level, so a bulk run shows one notification rather than one per script. The prompt offers "Update Extension" and "Don't Show Again"; the latter persists in global state. Dismissing the notification without choosing an action suppresses it for the session only. Version handling notes: - Thresholds are per channel because the two lines interleave numerically: pre-release 2026.5.x/2026.7.x sort above stable 2026.4.0, so a single threshold cannot express both. Channel is inferred from the patch component, since VS Code does not populate __metadata.preRelease for installed extensions. Both inference rules were checked against every published version above the relevant floors. - PEP 440 ordering is used, as these extensions are not semver. - Dev builds are skipped rather than compared. "2026.7.0-dev" is valid PEP 440 and normalizes to 2026.7.0.dev0, which sorts below every real build of the same minor, so comparing it would flag anyone running a local build. - A missing extension is not treated as outdated; Pylance is optional. - Pylance is skipped unless python.languageServer selects it, so Jedi and None users are not asked to update an unused extension. Each outdated combination has its own complete l10n string rather than a joined extension list, since conjunctions and word order are locale-specific. The thresholds record the newest version on each channel that lacks the required change, and must be refreshed if either extension ships another release before the change lands. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
🔒 Automated review in progress — Rich Chiodo (@rchiodo) is auto-reviewing this PR. |
| notifyInlineScriptSetupOutcome(uri, routing); | ||
| return; | ||
| } | ||
| await promptUpdateExtensionsForInlineScripts(); |
There was a problem hiding this comment.
Warning · Non-blocking recommendation
If the advisory prompt rejects after environment creation, this handler reports setup as failed. Isolate prompt failures from the setup try block, matching the bulk path, and add a rejection test.
| Common.dontShowAgain, | ||
| ); | ||
|
|
||
| if (result === InlineScriptStrings.updateExtension) { |
There was a problem hiding this comment.
Warning · Non-blocking recommendation
When both extensions are outdated, the combined prompt names both but outdated[0] opens only Python, and the session latch prevents another prompt. Provide separate actions or make every named update reachable.
|
Result: 🔴 Verification detailsVerification: Isolated verification observed failures that were not classified as caused by this PR: Offline npm dependency bootstrap. The relevant tests could not be fully run in the isolated environment; this review is not fully verified. Summary: No meaningful tests could run because the container lacked Node.js, npm, and `node_modules`; `npm ci --offline` failed with `npm: not found`. The two targeted inline-script unit suites were therefore not run. I found 37 newly added tests covering version classification, thresholds, language-server gating, and prompt actions. Explicit coverage is still missing for invoking the prompt from the single-file and bulk setup entry points. Test runs: 1 passed, 1 failed, 2 not run
|
Rich Chiodo (rchiodo)
left a comment
There was a problem hiding this comment.
Approved via Review Center.
db0a3b2
into
microsoft:main
…nments (#1780) > Part of #1602. Design: #1601. Follow-up to #1772. ## Summary Fix PEP 723 inline-script cache and routing issues, make cached environments read-only through the package-management UI, and support deleting one cached environment without clearing the entire script cache. The feature remains behind the internal, undeclared `python-envs.inlineScripts.enabled` flag, which defaults to `false` and is latched at activation. This does not enable inline scripts by default or add automatic installation on Run/Debug. ## Commits and user-visible changes | Commit | Problem or missing behavior | Change | |---|---|---| | `64960273` | The first package enumeration could be mistaken for an installation, so expanding Packages or refreshing inventory invalidated a working script environment. Case-only Windows renames could also lose the association. | Replace package-event-based drift detection with a recorded inventory hash; hide package mutation actions and reject the palette install path for inline environments; preserve Windows case-only rename associations, including dirty aliases and pending reads. | | `669ffe18` | A lookup could detect drift and return no environment while routing still advertised the cache as valid and the setup CodeLens remained hidden. Other scripts sharing the entry could retain stale state. | Confirm drift against the current entry, invalidate affected routing/cached validation, and notify environment consumers without requiring a save. Protect newer repairs and selections from stale observations. | | `1d3ea92a` | Delete Environment was offered in the Projects view, but the inline manager did not implement removal and returned "Remove Environment not supported". | Delete the selected cache entry without a confirmation dialog, clear its known workspace associations, and update the environment collection. Preserve script files, project entries/settings, the base interpreter, and unrelated cache entries. | ## Implementation and behavior notes ### Record package inventory instead of interpreting package events - When inventory is readable, setup records an `installedPackagesHash` in the entry's `.meta.json`, under the existing cache-entry lock. The hash is computed from sorted, case-normalized `*.dist-info` names, which include distribution names and versions. - Validation and reuse compare the recorded inventory with disk. Merely expanding or refreshing the package list no longer has authority to invalidate the environment. - A missing baseline or unreadable inventory is **unknown**, not evidence that packages were added. Existing `manuallyModified` sidecars remain honored. - The package list stays visible, but inline environments do not offer Install, Uninstall, or Change Version actions in the trees. The palette install command explains that dependencies should be edited in the script's metadata and setup rerun. - No new package-provider event metadata, per-package operation queues, or provider opt-in interfaces are introduced. The pip, Conda, Poetry, shared package-change helper, and package-watcher implementations are unchanged. ### Keep routing and the setup action consistent - A confirmed mismatch clears the affected scripts' cached environments and validation state, advances revisions, restores their setup CodeLenses, and publishes environment changes. - Invalidation rechecks the current sidecar/inventory under a non-waiting cache-entry lock. An observation made before a repair must not invalidate the repaired entry. - Busy entries and unavailable confirmation reads do not publish stale invalidations. A newer selection wins when an older lookup finishes later. - Detection is on demand, after the existing five-second validation cache expires; this is not a new background polling service. ### Delete one cached environment - Delete means physical deletion whether the entry has one associated script or several. It is not silently changed into "detach this script" for shared entries. - The action has **no confirmation dialog**. Scripts sharing the deleted entry need setup again. - Reuse the existing cache maintenance, ownership/path checks, locks, and association-cleanup machinery, with a single-entry target. Do not invoke the bulk project-settings cleanup. - Route an inline Projects-view item through the manager owning the clicked environment, rather than the file's potentially changed fallback manager. Ordinary environment removal keeps its existing routing. - Reject missing arguments, foreign/out-of-cache targets, redirected entries, and active creation conflicts. Preserve unrelated and unrecognized association records. - Invalidate a valid sidecar before filesystem removal so an interrupted deletion does not leave surviving cache files trusted as healthy. Report filesystem/persistence failures and update local state for what was actually removed. ## Testing Local validation before the rebase/signing update (`1afe4bc9`): - `npm run compile-tests`: passed. - `npm run compile` (webpack extension bundle): passed. - `npm run lint`: passed. - Full unit suite: **2,074 passing, 6 pending**. Coverage includes get-only drift detection and CodeLens recovery, shared versus unrelated entries, stale observations after newer repairs/selections, real cache-entry locks, unavailable inventory, Windows case-only renames, palette restrictions, targeted deletion without prompting, protected source/settings/base-Python files, already-missing entries, unsafe paths, partial failures, and concurrent create/delete/rehydration. ### Suggested manual verification These are reviewer checks, not a claim that a full live VS Code end-to-end pass has been completed: 1. Enable `python-envs.inlineScripts.enabled` and reload. Use the Python Environments integration and companion builds that support per-file interpreters. 2. Set up two scripts with the same metadata so they share a cached environment, plus a third script using a different cache entry. 3. Expand/refresh Packages: setup remains valid and inline package mutation actions are absent. The palette install action for an inline project should show guidance rather than install. 4. In a disposable test environment, change installed packages externally. After the validation cache expires, request the script's environment without saving. Verify the affected setup actions return and explicit setup repairs the entry. 5. On Windows, rename `job.py` to `Job.py`; verify the existing environment remains associated and unsaved metadata is not replaced with stale disk metadata. 6. Delete a shared inline environment from the Projects view. Verify there is no confirmation, only that cache entry is deleted, affected scripts need setup again, and the unrelated environment, scripts, project settings, and base Python remain. 7. Exercise a busy/failed deletion and verify it reports failure rather than success. Recheck normal package management/removal for an ordinary venv. ## Scope and known follow-ups - Inventory comparison is not a full package-integrity audit: it does not inspect package contents or legacy `.egg-info`-only installations. - Shared cache entries can be referenced by other workspaces. Association cleanup/notifications here are local to the current workspace; other windows notice deletion on revalidation. Cache locks do not track arbitrary running Python processes, so running jobs should be stopped before deletion. - The current unknown-inventory behavior is retained. The case where an entirely missing `site-packages` directory is accepted during reuse remains a follow-up; deleting the cache entry and running setup provides a recovery path. - A rejected inline selection can still briefly show the temporary "selected" badge; the actual selection is not changed. That feedback fix is separate from the routing invalidation addressed here. - Companion-version guidance is covered separately by #1777. No installer-backend, retention-policy, or Run/Debug auto-setup changes are included. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a4277008-9dd3-4beb-b9ac-4197eb085404
Problem
A PEP 723 inline-script environment is built correctly by this extension on its own, but it only reaches the language server once two companion changes ship:
exactResource, Use exact per-file environments for Python files (PEP 723 PR 19) vscode-python#26129)python/didChangeFilePythonPathnotification (PR #9302)Until both are present, setup reports success and the user is left without the full inline script experience, with nothing explaining why.
Change
Warn once, after a successful setup rather than before one. That is the moment the gap becomes visible, and the creation progress notification is already gone — prompting up front would be noise stacked on top of it.
Both entry points prompt at the handler level (
setupInlineScriptEnvironmentHandlerandsetUpInlineScriptEnvironmentsInWorkspace), so a bulk run over N scripts shows one notification rather than N.The prompt offers Update Extension and Don't Show Again; the latter persists in global state. Dismissing without choosing an action suppresses it for the session only.
Version handling
2026.4.02026.7.20260826012026.3.12026.3.101Values are the newest version on each channel that still lacks the change; anything newer passes.
Notes on why this is more involved than a single
>=:2026.5.x/2026.7.xsort above stable2026.4.0, so one threshold cannot express both — a future stable such as2026.6.0would be wrongly rejected.__metadata.preReleasefor installed extensions (verified empty on every installed extension locally, including a known pre-release build). Python pre-releases carry a build-date patch, Pylance pre-releases use a 100+ counter. Both rules were checked against every published version above the relevant floors.2026.7.0-devis valid PEP 440 and normalizes to2026.7.0.dev0, which sorts below every real build of the same minor — comparing it would flag anyone running a local build of either extension.python.languageServerselects it, so Jedi and None users are not asked to update an extension they do not use.Each outdated combination has its own complete l10n string rather than a joined extension list, since conjunctions and word order are locale-specific.
Follow-up
The thresholds are a snapshot of the newest version lacking the change. If either extension ships another release before the change lands, the corresponding constant must be bumped, or the warning silently stops firing for users on that release.
Once the real shipping versions are known, consider inverting to "first version with the change" (
<instead of<=): that framing fails toward an over-eager prompt, which is visible and reported, rather than toward silence.Testing
36 new unit tests in
src/test/features/inlineScript/extensionVersionCheck.unit.test.ts, covering both channels for both extensions, the dev-build case, the language-server gate, and all dismissal paths.Full unit suite: 2047 passing, 0 failing (clean
out/rebuild).tsc --noEmit,eslint src, andprettier --checkall clean.